#ifndef SRC_HYPERCUBE_HYPERCUBE_CRYTO_AGENT
#define SRC_HYPERCUBE_HYPERCUBE_CRYTO_AGENT

#include "crypto_helper.hpp"
#include "cube_signature.hpp"

// * hypercube_hmmcs:
//   * hypercube  : hypercube encoding
//   * hmm        : homomorphic matrix multiplication
//   * cs         : cryptograph server
class hypercube_hmmcs : public hmm_crypto_agent {
private:
  hypercube_hmmcs() = delete;

  std::shared_ptr<helib::Context>
  build_context(const params<helib::BGV> &params, long m);

public:
  /**
   * @brief Construct a new `hypercube_hmmcs` object
   *        1D/2D hypercube
   * @param params      BGV params
   * @param eh_nrow     Expected # of hypercube rows
   * @param eh_ncol     Expected # of hypercube columns
   * @param linear      Using 1D hypercube
   * @param cache_free  Not using HElib context cache
   * @param minimal_m   Find the minimal m in [mlb, mup]
   *                    (See params<helib::BGV>)
   * @param verbose     Print out some important infos
   */
  hypercube_hmmcs(const params<helib::BGV> &params, const cube_signature &cube,
                  bool cache_free = false, bool minimal_m = false,
                  bool verbose = false);

  /**
   * @brief Construct a new `hypercube_hmmcs` object
   *        Read from the stream the serialized `Context` object in
   *        binary format
   * @param str   Input `std::istream`
   */
  hypercube_hmmcs(std::istream &str) : hmm_crypto_agent(str) {}

  /**
   * @brief Construct a new `hypercube_hmmcs` object
   *        Building the HElib context externally.
   *        * Must provide the building time
   * @param context HElib context
   * @param time    Escaping time of building HElib context
   */
  hypercube_hmmcs(std::shared_ptr<helib::Context> context, double time)
      : hmm_crypto_agent(context, time) {}

  /**
   * @brief Print out algebra and other important infos
   *        about the HElib context
   */
  void report_context(std::ostream &os = std::cout) const override;

  // See below
  void report_time() const override;
  double setup_time() const { return setup_time_; }
  double find_params_time() const { return find_params_time_; }

protected:
  cube_signature hypercube_;

  // * setup_time
  //   * find_params_time (m, plaintext modulus & generators)
  //   * build_context_time (HElib context) <- hmm_crypto_agent
  double setup_time_ = 0.0;
  double find_params_time_ = 0.0;
};

// * hypercube_hmmcc:
//   * hypercube  : hypercube encoding
//   * hmm        : homomorphic matrix multiplication
//   * cc         : cryptograph client
// * (client is a special agent with `Encrypt & Decrypt` functions)
// * only BGV supports hypercube packing
class hypercube_hmmcc : public hypercube_hmmcs,
                        public hmm_crypto_client<helib::BGV> {
private:
  hypercube_hmmcc() = delete;

public:
  /**
   * @brief Construct a new `hypercube_hmmcc` object
   *        See the constructor of `hypercube_hmmcs` for more details
   */
  hypercube_hmmcc(const params<helib::BGV> &params, const cube_signature &cube,
                  bool cache_free = false, bool minimal_m = false);

  /**
   * @brief Construct a new `hypercube_hmmcc` object
   *        using a hypercube_hmmcs instance
   * @param hmmcs HMM cryptograph server
   */
  hypercube_hmmcc(const hypercube_hmmcs &hmmcs);

  // See below
  void report_time() const override;

  /**
   * @brief Implementation of the encryption method for `hmmcc`
   *        based on hypercube-structure packing technology
   *        See `hmm_crypto_client` in crypto_helper.hpp for more details
   */
  helib::Ctxt encrypt(const NTL::mat_ZZ &mat, ENC type = PUBKEY) override;
  void encrypt(helib::Ctxt &ctxt, const NTL::mat_ZZ &mat, ENC type);
  helib::Ctxt encrypt(const std::vector<long> &vec, ENC type = PUBKEY) override;
  helib::Ctxt encrypt(const std::vector<double> &vec,
                      ENC type = PUBKEY) override;

  void decrypt(NTL::mat_ZZ &mat, const helib::Ctxt &cmat) override;
  void decrypt(std::vector<long> &vec, const helib::Ctxt &cmat) override;
  void decrypt(std::vector<double> &vec, const helib::Ctxt &cmat) override;

protected:
  // * setup_time           <- hypercube_hmmcs
  //   * find_params_time   <- hypercube_hmmcs
  //   * build_context_time <- hypercube_hmmcs
  //   * key_gen_time       <- hmm_crypto_client
};

#endif /* SRC_HYPERCUBE_HYPERCUBE_CRYTO_AGENT */
